package com.nulldev.util.uri;

import java.io.IOException;
import java.io.InputStream;
import java.net.Proxy;
import java.net.URL;
import java.net.URLConnection;
import java.net.URLStreamHandler;
import java.net.URLStreamHandlerFactory;
import java.security.PrivilegedAction;
import java.util.Hashtable;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.StringTokenizer;

import com.nulldev.util.java.JVMUtil;
import com.nulldev.util.java.swing.extSwing.b64Pane.DataConnection;
import com.nulldev.util.uri._exposed.HTTPSUrlConnectionImpl;

import sun.net.www.protocol.http.HttpURLConnection;

public class nStreamHandlerFactory implements URLStreamHandlerFactory {
	
	public static class BlankInputStream extends InputStream {

		@Override
		public int read() throws IOException {
			return -1;
		}
		
	}
	
	public static class BlankStream extends URLConnection {

		protected BlankStream(URL var1) {
			super(var1);
		}

		@Override
		public void connect() throws IOException {
			this.connected = true;
		}
		
		@Override
	    public InputStream getInputStream() throws IOException {
			return new BlankInputStream();
		}

	}
	
	public static class BlankStreamHTTP extends HttpURLConnection {

		public BlankStreamHTTP(URL u, Proxy p) throws IOException {
			super(u, p);
		}

		@Override
		public void connect() throws IOException {
			this.connected = true;
		}
		
		@Override
	    public InputStream getInputStream() throws IOException {
			return new BlankInputStream();
		}

	}

	private static final LinkedHashMap<String, URLStreamHandler> registeredProtocols = 
			new LinkedHashMap<String, URLStreamHandler>();
	public static final nStreamHandlerFactory INSTANCE = new nStreamHandlerFactory();
	private static boolean init;
	
	/*
	 * 
	 * TODO:
	 * Add the following protocols:
	 * QCLP:
	 * 	qclp://__peer_id.qclp
	 *  qclp://PEER_ID
	 * ANN:
	 * 	ann://127.0.0.1:4444/__ann00.ann
	 *  ann://ANN Host/ANN Address
	 * AMBP: [IMPLEMENTED]
	 *  ambp://127.0.1.1:29292/
	 *  ambp://AMBP Listen Server/
	 * 
	 */
	
	static {
		if (!init && registeredProtocols.isEmpty()) {
			URL.setURLStreamHandlerFactory(INSTANCE);
			registerProtocol("data", new URLStreamHandler() {
				
				@Override
				protected URLConnection openConnection(URL u) throws IOException {
					return new DataConnection(u);
				}
			});
			registerProtocol("blank", new URLStreamHandler() {
				
				@Override
				protected URLConnection openConnection(URL u) throws IOException {
					return new BlankStream(u);
				}
			});
			init = true;
		}
	}
	
	public static boolean registerProtocol(final String protocol, final URLStreamHandler handler) {
		registeredProtocols.put(protocol, handler);
		return true;
	}
	
	@Override
	public URLStreamHandler createURLStreamHandler(String protocol) {
		if (this.registeredProtocols.containsKey(protocol)) {
			return this.registeredProtocols.get(protocol);
		}
		return getURLStreamHandler(protocol);
	}

	final static URLStreamHandlerFactory factory = null;
    static Hashtable<String, URLStreamHandler> handlers = new Hashtable<String, URLStreamHandler>();
    private static Object streamHandlerLock = new Object();
    private static final String protocolPathProp = "java.protocol.handler.pkgs";
    
    static URLStreamHandler getURLStreamHandler(String protocol) {
        URLStreamHandler handler = handlers.get(protocol);
        if (handler == null) {

            boolean checkedWithFactory = false;

            // Use the factory (if any)
            if (factory != null) {
                handler = factory.createURLStreamHandler(protocol);
                checkedWithFactory = true;
            }

            // Try java protocol handler
            if (handler == null) {
                String packagePrefixList = null;

                packagePrefixList
                    = java.security.AccessController.doPrivileged(
                    (PrivilegedAction<String>) new sun.security.action.GetPropertyAction(
                        protocolPathProp,""));
                if (packagePrefixList != "") {
                    packagePrefixList += "|";
                }

                // REMIND: decide whether to allow the "null" class prefix
                // or not.
                packagePrefixList += "sun.net.www.protocol";

                StringTokenizer packagePrefixIter =
                    new StringTokenizer(packagePrefixList, "|");

                while (handler == null &&
                       packagePrefixIter.hasMoreTokens()) {

                    String packagePrefix =
                      packagePrefixIter.nextToken().trim();
                    try {
                        String clsName = packagePrefix + "." + protocol +
                          ".Handler";
                        Class<?> cls = null;
                        try {
                            cls = Class.forName(clsName);
                        } catch (ClassNotFoundException e) {
                            ClassLoader cl = ClassLoader.getSystemClassLoader();
                            if (cl != null) {
                                cls = cl.loadClass(clsName);
                            }
                        }
                        if (cls != null) {
                            handler  =
                              (URLStreamHandler)cls.newInstance();
                        }
                    } catch (Exception e) {
                        // any number of exceptions can get thrown here
                    }
                }
            }

            synchronized (streamHandlerLock) {

                URLStreamHandler handler2 = null;

                // Check again with hashtable just in case another
                // thread created a handler since we last checked
                handler2 = handlers.get(protocol);

                if (handler2 != null) {
                    return handler2;
                }

                // Check with factory if another thread set a
                // factory since our last check
                if (!checkedWithFactory && factory != null) {
                    handler2 = factory.createURLStreamHandler(protocol);
                }

                if (handler2 != null) {
                    // The handler from the factory must be given more
                    // importance. Discard the default handler that
                    // this thread created.
                    handler = handler2;
                }

                // Insert this handler into the hashtable
                if (handler != null) {
                    handlers.put(protocol, handler);
                }

            }
        }

        if (handler.getClass() == sun.net.www.protocol.http.Handler.class) {
        	//JVMUtil.async_println("Filtered http stream");
        	return new FilteredHTTPHandler();
        }else if (handler.getClass() == sun.net.www.protocol.https.Handler.class) {
        	//JVMUtil.async_println("Filtered https stream");
        	return new FilteredHTTPSHandler();
        }
        //JVMUtil.async_println("skipped. " + handler);
        return handler;

    }
    
    public static class FilteredHTTPHandler extends URLStreamHandler {

		@Override
		protected URLConnection openConnection(final URL u) throws IOException {
			if (!allowConnection(u, Proxy.NO_PROXY)) {
				return new BlankStreamHTTP(u, Proxy.NO_PROXY);
			}
			return new HttpURLConnection(u, Proxy.NO_PROXY);
		}
		
		@Override
		protected URLConnection openConnection(final URL u, final Proxy proxy) throws IOException {
			if (!allowConnection(u, proxy)) {
				return new BlankStreamHTTP(u, proxy);
			}
			return new HttpURLConnection(u, proxy);
		}
    	
    }
    
    public static class FilteredHTTPSHandler extends URLStreamHandler {

		@Override
		protected URLConnection openConnection(URL u) throws IOException {
			if (!allowConnection(u, Proxy.NO_PROXY)) {
				return new BlankStreamHTTP(u, Proxy.NO_PROXY);
			}
			final sun.net.www.protocol.https.Handler h = new sun.net.www.protocol.https.Handler();
			return new HTTPSUrlConnectionImpl(u, Proxy.NO_PROXY, h);
		}
		
		@Override
		protected URLConnection openConnection(URL u, Proxy p) throws IOException {
			if (!allowConnection(u, p)) {
				return new BlankStreamHTTP(u, p);
			}
			final sun.net.www.protocol.https.Handler h = new sun.net.www.protocol.https.Handler();
			return new HTTPSUrlConnectionImpl(u, p, h);
	    }
    	
    }
    
    private static boolean allowConnection(final URL url, final Proxy proxy) {
    	for (final nFilter filter : filters) {
    		if (!filter.allowConnection(url, proxy)) {
    			return false;
    		}
    	}
    	return true;
    }
    
    private static final LinkedList<nFilter> filters = new LinkedList<nFilter>();
    
    public static interface nFilter {
    	//TODO: Move to nFilterAction. Actions Supported: allow(), redirectTo(url, proxy), block()
    	public boolean allowConnection(final URL url, final Proxy proxy);
    }
    
    public static boolean addFilter(final nFilter filter) {
    	return filters.add(filter);
    }

	public static void install() {
		// invokes static {}
	}
	
	/**
	 * Shows off nFilter allowing users to control HTTP and HTTPS requests. <br>
	 * This demo will block www.google.com until exited.
	 * @param args
	 * @throws IOException
	 */
	public static void main(String[] args) throws IOException {
		install();
		addFilter(new nFilter() {

			@Override
			public boolean allowConnection(URL url, Proxy proxy) {
				JVMUtil.async_println(url);
				if (url.toString().contains("www.google.com")) {
					JVMUtil.async_println("Blocked connection to " + url + ".");
					return false;
				}
				return true;
			}
			
		});
		URIUtil.downloadPage(URIUtil.safeCreate("https://www.google.com"));
		URIUtil.downloadPage(URIUtil.safeCreate("http://google.com"));
	}
}
